#include <iostream>
#include <map>

#include <boost/utility.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/unordered_map.hpp>
#include <boost/random.hpp>

#include <kuai/misc/thread.hpp>
#include <kuai/misc/error.hpp>
#include <kuai/mol/mol.hpp>
#include <kuai/mol/ct.hpp>
#include <kuai/mol/molid.hpp>


#ifndef _KUAI_SIM_PHASE_SPACE_H_
#define _KUAI_SIM_PHASE_SPACE_H_

namespace kuai {


	class SimObject {
	public:
		SimObject()
		{ }
		virtual ~SimObject()
		{ }
	};

	class PhaseSpace;
	typedef SimObject* (*SimObjectCreator)(PhaseSpace&);
	typedef boost::shared_ptr<SimObject> SimObjPtr;

	class SimObjFactory
		: public boost::noncopyable
	{
	private:
		SimObjFactory();

	public:
		static SimObjFactory& getInstance();

		boost::shared_ptr<SimObject> create(const String& name, PhaseSpace& space) const ;

	private:
		std::map<String, SimObjectCreator> funcs;
	};

	template<typename T>
	class ArrayProperty
		: public SimObject
	{
	public:
		explicit ArrayProperty(const std::vector<T>& data0)
			: data(data0)
		{ }

		explicit ArrayProperty(std::vector<T>& data0, bool swap)
		{ 
			if (swap) {
				data.swap(data0);
			}
			else {
				data = data0;
			}
		}

		explicit ArrayProperty(const T* p0, size_t n)
			: data(p0, p0+n)
		{ }

		explicit ArrayProperty(size_t n, const T& v0=T())
			: data(n, v0)
		{  }

	public:
		const T* c_ptr() const {
			return &data[0];
		}

		T* x_ptr() {
			return &data[0];
		}
		size_t size() const {
			return data.size();
		}

	protected:
		std::vector<T> data;
	};

	typedef ArrayProperty<XYZ> PropertyXYZ;
	typedef ArrayProperty<Index> PropertyIndex;
	typedef ArrayProperty<Integer> PropertyInteger;
	typedef ArrayProperty<RealNumber> PropertyRealNumber;

	class PropertyConnectTable
		: public PropertyIndex
	{
	public:
		PropertyConnectTable() 
			: PropertyIndex(0)
		{ }
		explicit PropertyConnectTable(const Index buffer[]);

		operator const ConnectTable&() const {
			return ct;
		}

		const ConnectTable& c_ct() const {
			return ct;
		}

		ConnectTable& x_ct() {
			return ct;
		}

	protected:
		ConnectTable ct;
	};

	template<typename T>
	class SingleProperty 
		: public SimObject
	{
	public:
		SingleProperty(const T& v)
			: data(v)
		{ }
		SingleProperty()
		{ }

		const T* c_ptr() const {
			return &data;
		}

		T* x_ptr() {
			return &data;
		}

		const T& c_ref() const {
			return data;
		}

		T& x_ref() {
			return *x_ptr();
		}

		virtual bool save(std::ostream& stream) const {
			stream.write(reinterpret_cast<const char*>(c_ptr()), sizeof(T));
			return stream.good();
		}

		virtual bool load(std::istream& stream) {
			stream.read(reinterpret_cast<char*>(x_ptr()), sizeof(T));
			return stream.good();
		}

		virtual void set(const String& key, const String& d0) {
			if (key == "") {
				data = lexical_cast<T>(d0);
			}
			else {
				throw UNDEFINED_MEMBER(key);
			}
		};
		virtual String get(const String& key) const {
			if (key == "") {
				return str(data);
			}
			else {
				throw UNDEFINED_MEMBER(key);
			}
		};

	protected:
		T data;
	};

	typedef SingleProperty<PBC> PropertyPBC;


	class PhaseSpace
		: public boost::noncopyable
	{
	public:
		typedef std::vector<std::pair<String, SimObjPtr> > ContainerType;
		typedef ContainerType::size_type size_type;
		typedef ContainerType::const_iterator const_iterator;
		typedef ContainerType::iterator iterator;

	public:
		PhaseSpace()
		{ }
		virtual ~PhaseSpace()
		{ }
		
		Index countAtoms() const {
			const_iterator it = find(MOL_INFO_CT);
			if (it != end()) {
				const ConnectTable& ct = static_cast<const PropertyConnectTable&>(*(it->second));
				return ct.countAtoms();
			}
			else {
				return 0;
			}
		};
		Index countBonds() const {
			const_iterator it = find(MOL_INFO_CT);
			if (it != end()) {
				const ConnectTable& ct = static_cast<const PropertyConnectTable&>(*(it->second));
				return ct.countBonds();
			}
			else {
				return 0;
			}
		};

		size_type size() const {
			return items.size();
		}
		const_iterator begin() const {
			return items.begin();
		}

		const_iterator end() const {
			return items.end();
		}

		iterator begin() {
			return items.begin();
		}

		iterator end() {
			return items.end();
		}

		SimObjPtr& operator[](const String& key);
	
		const_iterator find(const String& key) const;
		iterator find(const String& key);

		void insert(const String& key, SimObjPtr& data) {
			insert(std::make_pair(key, data));
		}
		void insert(std::pair<String, SimObjPtr>& data) {
			iterator it = find(data.first);
			if (it == end()) {
				indexes[data.first] = items.size();
				items.push_back(data);
			}
			else {
				it->second = data.second;
			}
		}

		template<typename PropertyType>
			PropertyType get(const String& key)
		{
			SimObjPtr& ptr = (*this)[key];
			return reinterpret_cast<PropertyType>(*ptr);
		}

		template<typename PropertyType>
			PropertyType get(const String& key) const
		{
			const_iterator it = this->find(key);
			if (it != end()) {
				return reinterpret_cast<PropertyType>(*(it->second));
			}
			else {
				throw std::range_error(key + " is not a memeber of pahse space");
			}
		}

		template<>
		inline Index* get<Index*>(const String& key)
		{
			PropertyIndex& p = get<PropertyIndex&>(key);
			return p.x_ptr();
		}
		template<>
		inline XYZ* get<XYZ*>(const String& key)
		{
			PropertyXYZ& p = get<PropertyXYZ&>(key);
			return p.x_ptr();
		}
		template<>
		inline RealNumber* get<RealNumber*>(const String& key)
		{
			PropertyRealNumber& p = get<PropertyRealNumber&>(key);
			return p.x_ptr();
		}
		template<>
		inline Integer* get<Integer*>(const String& key)
		{
			PropertyInteger& p = get<PropertyInteger&>(key);
			return p.x_ptr();
		}

		template<>
		inline const Index* get<const Index*>(const String& key) const 
		{
			const PropertyIndex& p = get<const PropertyIndex&>(key);
			return p.c_ptr();
		}
		template<>
		inline const XYZ* get<const XYZ*>(const String& key) const 
		{
			const PropertyXYZ& p = get<const PropertyXYZ&>(key);
			return p.c_ptr();
		}
		template<>
		inline const RealNumber* get<const RealNumber*>(const String& key) const 
		{
			const PropertyRealNumber& p = get<const PropertyRealNumber&>(key);
			return p.c_ptr();
		}
		template<>
		inline const Integer* get<const Integer*>(const String& key) const 
		{
			const PropertyInteger& p = get<const PropertyInteger&>(key);
			return p.c_ptr();
		}

		ConnectTable& ct() {
			PropertyConnectTable& rct = get<PropertyConnectTable&>(MOL_INFO_CT);
			return rct.x_ct();
		}
		const ConnectTable& ct() const {
			const PropertyConnectTable& rct = get<const PropertyConnectTable&>(MOL_INFO_CT);
			return rct.c_ct();
		}

	public:
		void swap(PhaseSpace& space) {
			indexes.swap(space.indexes);
			items.swap(space.items);
		}

	public:
		bool erase(const String& key);

	private:
		std::vector<std::pair<String, SimObjPtr> > items;
		std::map<String, Index> indexes;
	};

	inline void swap(kuai::PhaseSpace& v1, kuai::PhaseSpace& v2) 
	{
		v1.swap(v2);
	}

	inline SimObject* createPropertyConnectTable(PhaseSpace& space) {
		return new PropertyConnectTable;
	}

	template<typename T> 
	inline SimObject* createArrayProperty0(PhaseSpace& space) {
		return new ArrayProperty<T>(0);
	}

	template<typename T> 
	inline SimObject* createArrayPropertyOfAtoms(PhaseSpace& space) {
		return new ArrayProperty<T>(space.countAtoms());
	}

	template<typename T> 
	inline SimObject* createArrayPropertyOfBonds(PhaseSpace& space) {
		return new ArrayProperty<T>(space.countBonds());
	}

	template<typename T, size_t N> 
	inline SimObject* createArrayPropertyN(PhaseSpace& space) {
		return new ArrayProperty<T>(N);
	}

	template<typename T> 
	inline SimObject* createSingleProperty(PhaseSpace& space) {
		return new SingleProperty<T>;
	}

	template<typename T> 
	inline SimObject* createProperty(PhaseSpace& space) {
		return new T(space);
	}

	class BreakJob
		: public std::runtime_error
	{ 
	public:
		BreakJob(const String& err) 
			: std::runtime_error(err)
		{ }
	};

	Index setupAromaticBond(PhaseSpace& space);
	
	void calcBondNormVector(const ConnectTable& ct, const Bond& bondI, const XYZ coords[], XYZ result[]);
	void calcBondNormVector(const ConnectTable& ct, const XYZ coords[], XYZ result[]);
	

};

#endif
